<!doctype html>
<!--
@license
Copyright (c) 2017 The Polymer Project Authors. All rights reserved.
This code may only be used under the BSD style license found at http://polymer.github.io/LICENSE.txt
The complete set of authors may be found at http://polymer.github.io/AUTHORS.txt
The complete set of contributors may be found at http://polymer.github.io/CONTRIBUTORS.txt
Code distributed by Google as part of the polymer project is also
subject to an additional IP rights grant found at http://polymer.github.io/PATENTS.txt
-->
<html>
<head>
  <meta charset="utf-8">
  <script src="../../node_modules/@webcomponents/webcomponentsjs/webcomponents-bundle.js" register="true" shadydom="true"></script>
  <script src="wct-browser-config.js"></script>
  <script src="../../node_modules/wct-browser-legacy/browser.js"></script>
  <script type="module" src="../../polymer-legacy.js"></script>
</head>
<body>
<template></template>
<script type="module">
import { Polymer } from '../../polymer-legacy.js';
var template = document.querySelector('template');
Polymer({
  is: 'x-content-test',
  _template: template
});

/*
 * Test the `<slot>` element distribution algorithm by verifying the
 * resulting composed tree structure.
 */
function testRender(descr, hostInnerHtml, shadowRootHtml, expectedHtml) {
  test(descr, function() {
    // Create an instance of the test element.
    var host = document.createElement('x-content-test');
    // Populate the initial pool of light DOM children.
    host.innerHTML = hostInnerHtml;
    document.body.appendChild(host);
    // Pretend we're stamping the template contents.
    if (shadowRootHtml) {
      host.shadowRoot.innerHTML = shadowRootHtml;
    }
    // Invoke distribution and verify the resulting tree.
    ShadyDOM.flush();
    assert.strictEqual(getComposedHTML(host), expectedHtml);
    document.body.removeChild(host);
  });
  test(descr + ' fragment', function() {
    // Create an instance of the test element.
    var host = document.createElement('x-content-test');
    // Populate the initial pool of light DOM children.
    host.innerHTML = hostInnerHtml;
    document.body.appendChild(host);
    // Pretend we're stamping the template contents.
    var div = document.createElement('div');
    div.innerHTML = shadowRootHtml;
    var frag = document.createDocumentFragment();
    while (div.firstChild) {
      frag.appendChild(div.firstChild);
    }
    host.shadowRoot.appendChild(frag);
    // Invoke distribution and verify the resulting tree.
    ShadyDOM.flush();
    assert.strictEqual(getComposedHTML(host), expectedHtml);
    document.body.removeChild(host);
  });
}

testRender('Empty shadow', 'abc', '', '');
testRender('Simple shadow', 'abc', 'def', 'def');
testRender('Fallback shadow', 'abc',
           '<slot name="xxx">fallback</name>', 'fallback');
testRender('Fallback shadow, empty host', '',
           '<slot name="xxx">fallback</name>', 'fallback');
testRender('Content', 'abc',
           '<slot>fallback</slot>', 'abc');
testRender('Content before', 'abc',
           'before<slot>fallback</slot>', 'beforeabc');
testRender('Content after', 'abc',
           '<slot>fallback</slot>after', 'abcafter');

suite('render content', function() {
  testRender('no select', '<a href="">Link</a> <b>bold</b>',
             '<slot></slot>',
             '<a href="">Link</a> <b>bold</b>');
  testRender('select ""', '<a href="">Link</a> <b>bold</b>',
             '<slot></slot>',
             '<a href="">Link</a> <b>bold</b>');

  testRender('slot a',
             '<a slot="a">a</a> <a slot="b">b</a>',
             '<slot name="a"></slot>',
             '<a slot="a">a</a>');

  testRender('slot b a',
             '<a slot="a">a</a> <a slot="b">b</a>',
             '<slot name="b"></slot><slot name="a"></slot>',
             '<a slot="b">b</a><a slot="a">a</a>');

  testRender('slot b a 2',
             '<a slot="a">a</a> <a slot="b">b</a> <a slot="b">c</a>',
             '<slot name="b"></slot><slot name="a"></slot>',
             '<a slot="b">b</a><a slot="b">c</a><a slot="a">a</a>');

  testRender('slot c catchall',
             '<span>a</span><span>b</span><span slot="c">c</span><span>d</span>',
             '<slot name="c"></slot><slot></slot>',
             '<span slot="c">c</span><span>a</span><span>b</span><span>d</span>');
});


test('Reproject', function() {
  var host = document.createElement('x-content-test');
  document.body.appendChild(host);
  host.innerHTML = '<a></a>';
  // pre-distirbution grab first composed node.
  var a = getComposedChildAtIndex(host, 0);

  host.shadowRoot.innerHTML = '<x-content-test id="p"></x-content-test>';
  // force upgrade on polyfilled browsers
  var p = host.shadowRoot.firstChild;
  p.innerHTML = '<b slot="b"></b><slot slot="a"></slot>';
  var b = p.firstChild;
  var content = p.lastChild;

  p.shadowRoot.innerHTML =
      'a: <slot name="a"></slot>b: <slot name="b"></slot>';
  var textNodeA = p.shadowRoot.firstChild;
  var contentA = p.shadowRoot.childNodes[1];
  var textNodeB = p.shadowRoot.childNodes[2];
  var contentB = p.shadowRoot.childNodes[3];

  function testRender() {
    ShadyDOM.flush();
    assert.strictEqual(getComposedHTML(host),
        '<x-content-test id="p">a: <a></a>b: <b slot="b"></b></x-content-test>');

    assertArrayEqual(host.childNodes, [a]);
    assert.strictEqual(a.parentNode, host);
    assertArrayEqual(host.shadowRoot.childNodes, [p]);
    assert.strictEqual(p.parentNode, host.shadowRoot);
    assertArrayEqual(p.childNodes, [b, content]);
    assert.strictEqual(b.parentNode, p);
    assert.strictEqual(content.parentNode, p);
    assertArrayEqual(p.shadowRoot.childNodes,
        [textNodeA, contentA, textNodeB, contentB]);
  }

  testRender();
  testRender();
  document.body.removeChild(host);
});


suite('Mutate light DOM', function() {
  var host;

  setup(function() {
    host = document.createElement('x-content-test');
    document.body.appendChild(host);
  });

  teardown(function() {
    document.body.removeChild(host);
  });

  test('removeAllChildNodes - mutate host', function() {
    host.innerHTML = '<a>Hello</a>';

    host.shadowRoot.innerHTML = '<slot>fallback</slot>';
    ShadyDOM.flush();
    assert.strictEqual(getComposedHTML(host), '<a>Hello</a>');

    host.firstChild.textContent = '';
    ShadyDOM.flush();
    assert.strictEqual(getComposedHTML(host), '<a></a>');

    host.innerHTML = '';
    ShadyDOM.flush();
    assert.strictEqual(getComposedHTML(host), 'fallback');
  });


  test('removeAllChildNodes - mutate shadow', function() {
    host.innerHTML = '<a>Hello</a>';

    host.shadowRoot.innerHTML = '<slot></slot><b>after</b>';
    ShadyDOM.flush();
    assert.strictEqual(getComposedHTML(host), '<a>Hello</a><b>after</b>');

    host.shadowRoot.childNodes[1].textContent = '';
    ShadyDOM.flush();
    assert.strictEqual(getComposedHTML(host), '<a>Hello</a><b></b>');

    host.shadowRoot.innerHTML = '';
    ShadyDOM.flush();
    assert.strictEqual(getComposedHTML(host), '');
  });

  test('removeAllChildNodes - mutate shadow fallback', function() {
    host.innerHTML = '<a>Hello</a>';

    host.shadowRoot.innerHTML = '<slot name="xxx"><b>fallback</b></slot>';
    var b = host.shadowRoot.firstChild.firstChild;
    ShadyDOM.flush();
    assert.strictEqual(getComposedHTML(host), '<b>fallback</b>');

    b.textContent = '';
    ShadyDOM.flush();
    assert.strictEqual(getComposedHTML(host), '<b></b>');

    host.shadowRoot.firstChild.innerHTML = '';
    ShadyDOM.flush();
    assert.strictEqual(getComposedHTML(host), '');

    host.shadowRoot.innerHTML = '';
    ShadyDOM.flush();
    assert.strictEqual(getComposedHTML(host), '');
  });

  test('removeChild - mutate host', function() {
    host.innerHTML = '<a>Hello</a>';

    host.shadowRoot.innerHTML = '<slot>fallback</slot>';
    ShadyDOM.flush();
    assert.strictEqual(getComposedHTML(host), '<a>Hello</a>');

    host.firstChild.removeChild(host.firstChild.firstChild);
    ShadyDOM.flush();
    assert.strictEqual(getComposedHTML(host), '<a></a>');

    host.innerHTML = '';
    ShadyDOM.flush();
    assert.strictEqual(getComposedHTML(host), 'fallback');
  });

  test('removeChild - mutate host 2', function() {
    host.innerHTML = '<a></a><b></b>';
    var a = getComposedChildAtIndex(host, 0);
    var b = getComposedChildAtIndex(host, 1);

    host.shadowRoot.innerHTML = '<slot>fallback</slot>';
    ShadyDOM.flush();
    assert.strictEqual(getComposedHTML(host), '<a></a><b></b>');

    host.removeChild(b);
    ShadyDOM.flush();
    assert.strictEqual(getComposedHTML(host), '<a></a>');

    host.removeChild(a);
    ShadyDOM.flush();
    assert.strictEqual(getComposedHTML(host), 'fallback');
  });

  test('removeChild - mutate shadow', function() {
    host.innerHTML = '<a>Hello</a>';

    host.shadowRoot.innerHTML = '<slot></slot><b>after</b>';
    var b = host.shadowRoot.lastChild;
    ShadyDOM.flush();
    assert.strictEqual(getComposedHTML(host), '<a>Hello</a><b>after</b>');

    b.removeChild(b.firstChild);
    ShadyDOM.flush();
    assert.strictEqual(getComposedHTML(host), '<a>Hello</a><b></b>');

    host.shadowRoot.removeChild(b);
    ShadyDOM.flush();
    assert.strictEqual(getComposedHTML(host), '<a>Hello</a>');

    host.shadowRoot.removeChild(host.shadowRoot.firstChild);
    ShadyDOM.flush();
    assert.strictEqual(getComposedHTML(host), '');
  });

  test('setAttribute slot', function() {
    host.innerHTML = '<a slot="a">Hello</a><b slot="b">World</b>';


    host.shadowRoot.innerHTML = '<slot name="b">fallback b</slot>' +
                            '<slot name="a">fallback a</slot>';
    ShadyDOM.flush();
    assert.strictEqual(getComposedHTML(host), '<b slot="b">World</b><a slot="a">Hello</a>');

    host.shadowRoot.firstChild.setAttribute('name', 'xxx');
    ShadyDOM.flush();
    assert.strictEqual(getComposedHTML(host), 'fallback b<a slot="a">Hello</a>');

    host.shadowRoot.firstChild.setAttribute('name', '');
    host.shadowRoot.lastChild.setAttribute('name', '');
    ShadyDOM.flush();
    assert.strictEqual(getComposedHTML(host), 'fallback bfallback a');
  });

  test('appendChild - mutate host', function() {
    host.innerHTML = '<a>Hello</a>';
    host.shadowRoot.innerHTML = '<slot></slot>';
    ShadyDOM.flush();
    assert.strictEqual(getComposedHTML(host), '<a>Hello</a>');

    var b = document.createElement('b');
    host.appendChild(b);
    ShadyDOM.flush();
    assert.strictEqual(getComposedHTML(host), '<a>Hello</a><b></b>');
  });

  test('appendChild - mutate shadow', function() {
    host.innerHTML = '<a>Hello</a>';
    host.shadowRoot.innerHTML = '<slot></slot>';
    ShadyDOM.flush();
    assert.strictEqual(getComposedHTML(host), '<a>Hello</a>');

    var b = document.createElement('b');
    host.shadowRoot.appendChild(b);
    ShadyDOM.flush();
    assert.strictEqual(getComposedHTML(host), '<a>Hello</a><b></b>');
  });

  test('insertBefore - mutate host', function() {
    host.innerHTML = '<a>Hello</a>';
    var a = getComposedChildAtIndex(host, 0);
    host.shadowRoot.innerHTML = '<slot></slot>';
    ShadyDOM.flush();
    assert.strictEqual(getComposedHTML(host), '<a>Hello</a>');

    var b = document.createElement('b');
    host.insertBefore(b, a);
    ShadyDOM.flush();
    assert.strictEqual(getComposedHTML(host), '<b></b><a>Hello</a>');
  });

  test('insertBefore - mutate shadow', function() {
    host.innerHTML = '<a>Hello</a>';


    host.shadowRoot.innerHTML = '<slot></slot>';
    var content = host.shadowRoot.firstChild;
    ShadyDOM.flush();
    assert.strictEqual(getComposedHTML(host), '<a>Hello</a>');

    var b = document.createElement('b');
    host.shadowRoot.insertBefore(b, content);
    ShadyDOM.flush();
    assert.strictEqual(getComposedHTML(host), '<b></b><a>Hello</a>');
  });

  test('replaceChild - mutate host', function() {
    host.innerHTML = '<a>Hello</a>';
    host.shadowRoot.innerHTML = '<slot></slot>';
    ShadyDOM.flush();
    assert.strictEqual(getComposedHTML(host), '<a>Hello</a>');
    var b = document.createElement('b');
    host.replaceChild(b, host.firstChild);
    ShadyDOM.flush();
    assert.strictEqual(getComposedHTML(host), '<b></b>');
  });

  test('replaceChild - mutate shadow', function() {
    host.innerHTML = '<a>Hello</a>';
    host.shadowRoot.innerHTML = '<slot></slot>';
    var content = host.shadowRoot.firstChild;
    ShadyDOM.flush();
    assert.strictEqual(getComposedHTML(host), '<a>Hello</a>');
    var b = document.createElement('b');
    host.shadowRoot.replaceChild(b, content);
    ShadyDOM.flush();
    assert.strictEqual(getComposedHTML(host), '<b></b>');
  });

  test('querySelectorAll (shadowRoot)', function() {
    host.innerHTML = '<div id="main"></div>';
    host.shadowRoot.innerHTML = '<slot></slot><span id="main"></span>' +
      '<x-content-test></x-content-test>';
    // force upgrade on polyfilled browsers
    ShadyDOM.flush();
    var hostLocalMain = getComposedChildAtIndex(host.shadowRoot, 1);
    var child = getComposedChildAtIndex(host.shadowRoot, 100);
    child.innerHTML = '<div id="sub"></div>';
    var childLightSub = getComposedChildAtIndex(child, 0);
    child.shadowRoot.innerHTML = '<slot></slot><span id="sub"></span>';
    var childLocalSub = child.shadowRoot.lastChild;
    ShadyDOM.flush();
    assert.deepEqual(Array.from(host.root.querySelectorAll('span#main')), [hostLocalMain]);
    assert.deepEqual(Array.from(host.root.querySelectorAll('div#sub')), [childLightSub]);
    assert.deepEqual(Array.from(child.root.querySelectorAll('span#sub')), [childLocalSub]);
  });

  test('querySelectorAll (light dom)', function() {
    host.innerHTML = '<div id="main"></div>';
    var hostLightMain = getComposedChildAtIndex(host, 0);
    host.shadowRoot.innerHTML = '<slot></slot><span id="main"></span>' +
      '<x-content-test></x-content-test>';
    var child = getComposedChildAtIndex(host.shadowRoot, 100);
    child.innerHTML = '<div id="sub"></div>';
    var childLightSub = getComposedChildAtIndex(child, 100) ;
    child.shadowRoot.innerHTML = '<slot></slot><span id="sub"></span>';
    ShadyDOM.flush();
    assert.deepEqual(Array.from(host.querySelectorAll('div#main')), [hostLightMain]);
    assert.deepEqual(Array.from(host.querySelectorAll('#sub')), []);
    assert.deepEqual(Array.from(child.querySelectorAll('div#sub')), [childLightSub]);
  });

});

suite('Add shadowRoot to element with childNodes', function() {

  var el;

  setup(function() {
    el = document.createElement('div');
    el.testHTML = '<div slot="a">hi</div>';
    el.innerHTML = el.testHTML;
    el.attachShadow({mode: 'open'});
  });

  test('empty root', function() {
    ShadyDOM.flush();
    assert.equal(getComposedHTML(el), '');
  });

  test('simple root', function() {
    var shadow = '<div>shadow</div>';
    el.shadowRoot.innerHTML = shadow;
    ShadyDOM.flush();
    assert.equal(getComposedHTML(el), shadow);
  });

  test('slot matching', function() {
    var shadow = '<div><slot name="a"></slot></div>';
    el.shadowRoot.innerHTML = shadow;
    ShadyDOM.flush();
    assert.equal(getComposedHTML(el), '<div>' + el.testHTML + '</div>');
  });

  test('slot not matching', function() {
    var shadow = '<div><slot name="b"></slot></div>';
    el.shadowRoot.innerHTML = shadow;
    ShadyDOM.flush();
    assert.equal(getComposedHTML(el), '<div></div>');
  });

});

function getEffectiveChildNodes(node) {
  var list = [];
  var c$ = node.childNodes;
  for (var i=0, l=c$.length, c; (i<l) && (c=c$[i]); i++) {
    if (c.localName && (c.localName === 'content' || c.localName === 'slot')) {
      list = list.concat(c.assignedNodes({flatten: true}));
    } else {
      list.push(c);
    }
  }
  return list;
}

function getComposedHTML(node) {
  // Ignore shady CSS scoping
  return ShadyDOM.nativeTree.innerHTML(node).replace(/ class="[^"]*"/g, '');
}

function getComposedChildAtIndex(node, index) {
  var c$ = getEffectiveChildNodes(node);
  if (c$) {
    index = index || 0;
    index = Math.max(0, Math.min(index, c$.length-1));
    return c$[index];
  }
}

function assertArrayEqual(a, b, msg) {
  assert.equal(a.length, b.length, msg);
  for (var i = 0; i < a.length; i++) {
    assert.equal(a[i], b[i], msg);
  }
}
</script>
</body>
</html>
